package com.shareware.common.utils

import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.StatFs
import android.text.format.Formatter
import android.util.Log
import androidx.core.content.FileProvider
import com.shareware.common.DEFAULT
import com.shareware.common.getContextGlobal
import java.io.*
import java.nio.channels.FileChannel
import java.security.MessageDigest
import java.text.DecimalFormat
import java.util.*
import java.util.zip.*

/**
 * desc: 功能描述
 * email: mobiledeveloper@qq.com
 */

/**
 * 查看文件或者目录是否存在
 * @return file
 */
fun File.createFile(deleteOld: Boolean): File {
    if (exists()) {
        if (deleteOld) {
            deleteAll()
        }
        if (isFile) {
            try {
                createNewFile()
            } catch (ignore: IOException) {
            }
        }
    } else {
        if (isDirectory || name.lastIndexOf(".") == -1) {
            mkdirs()
        } else {
            val parentFile = parentFile
            if (parentFile != null && !parentFile.exists()) {
                parentFile.mkdirs()
            }
            try {
                createNewFile()
            } catch (ignore: IOException) {
            }
        }
    }
    return this
}

fun File.createDirDirect(deleteOld: Boolean): File {
    if (deleteOld) {
        deleteAll()
    }
    if (!exists()) {
        mkdirs()
    }
    return this
}

fun File.deleteAll() {
    if (!exists()) {
        return
    }
    if (isDirectory) {
        listFiles()?.forEach {
            if (isDirectory) {
                it.deleteAll()
            }
            it.delete()
        }
    }
    delete()
}

/**
 * @param sourceFiles 源文件集
 * @param zipFile zip文件
 */
@JvmOverloads
fun zip(sourceFiles: List<File>, zipFile: File, uncompressed: Boolean = false) {
    //zip输出流
    tryMethodIgnore {
        val parentFile = File(zipFile.parentFile, "zip").createFile(true)
        ZipOutputStream(FileOutputStream(zipFile)).use { zipOutStream ->
            //缓存输出流
            sourceFiles.forEach {
                copyFile(it, File(parentFile, it.name).createFile(true))
            }
            compress(zipOutStream, parentFile, "", uncompressed)
            zipOutStream.finish()
        }
        parentFile.deleteAll()
    }
}

fun compress(
    zipOutStream: ZipOutputStream,
    sourceFile: File,
    dir: String = "",
    uncompressed: Boolean = false,
) {
    var pathName = dir
    if (sourceFile.exists()) {
        pathName += sourceFile.name
        if (sourceFile.isDirectory) {
            pathName += File.separator
            val fileList = sourceFile.listFiles()
            if (!fileList.isNullOrEmpty()) {
                fileList.forEach {
                    compress(zipOutStream, it, pathName, uncompressed)
                }
            }
        } else {
            val entry = ZipEntry(pathName)
            if (uncompressed) {
                val file = File(pathName)
                entry.method = ZipEntry.STORED
                entry.size = file.length()
                entry.crc = calFileCRC32(file)
            }
            zipOutStream.putNextEntry(ZipEntry(pathName))
            FileInputStream(sourceFile).use { fis ->
                BufferedInputStream(fis).use { bis ->
                    var length: Int
                    val buffer = ByteArray(2048)
                    while (bis.read(buffer).also { length = it } != -1) {
                        zipOutStream.write(buffer, 0, length)
                    }
                    zipOutStream.closeEntry()
                }
            }
        }
    }
}

@Throws(IOException::class)
private fun calFileCRC32(file: File): Long {
    FileInputStream(file).use {
        CheckedInputStream(it, CRC32()).use { checksum ->
            while (checksum.read() != -1) {
            }
            return checksum.checksum.value
        }
    }
}

fun copyFile(source: File, newFile: File) {
    if (!source.exists() || source.length() == 0L) {
        return
    }
    if (!newFile.exists()) {
        newFile.createFile(false)
    }
    RandomAccessFile(source, "r").channel.use { inChannel ->
        val buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size())
        RandomAccessFile(newFile, "rw").channel.use { outChannel ->
            outChannel.write(buffer)
        }
    }
}

/**
 * 获取SD卡剩余空间大小，单位B
 *
 * @return 返回-1，说明没有安装sd卡
 */
fun getFreeDiskSpace(): Long {
    val state = Environment.getExternalStorageState()
    var freeSpace: Long = 0
    if (state == Environment.MEDIA_MOUNTED) {
        try {
            val filePath = Environment.getExternalStorageDirectory()
            val statFs = StatFs(filePath.path)
            val blockSize = statFs.blockSizeLong
            val availableBlocks = statFs.availableBlocksLong
            freeSpace = blockSize * availableBlocks
        } catch (e: Exception) {
            e.printStackTrace()
        }
    } else {
        return -1
    }
    return freeSpace
}

/**
 * 获取SD卡总空间大小，单位B
 *
 * @return 返回-1，说明没有安装sd卡
 */
fun getTotalDiskSpace(): Long {
    val state = Environment.getExternalStorageState()
    var totalSpace: Long = 0
    if (state == Environment.MEDIA_MOUNTED) {
        try {
            val path = Environment.getExternalStorageDirectory()
            val statFs = StatFs(path.path)
            val totalBlocks = statFs.blockCountLong
            val block = statFs.blockSizeLong
            totalSpace = block * totalBlocks
        } catch (e: Exception) {
            e.printStackTrace()
        }
    } else {
        return -1
    }
    return totalSpace
}

/**
 * 转换文件大小
 *
 * @param size 文件大小
 * @return B/KB/MB/GB
 */
fun formatFileSize(size: Long): String {
    val df = DecimalFormat("##.##")
    return when {
        size < 1024 -> df.format(size.toDouble()) + "B"
        size < 1048576 -> df.format(size.toDouble() / 1024) + "KB"
        size < 1073741824 -> df.format(size.toDouble() / 1048576) + "MB"
        else -> df.format(size.toDouble() / 1073741824) + "G"
    }
}

fun formatFileSize2(size: Long): String {
    return Formatter.formatFileSize(getContextGlobal(), size)
}

/**
 * 获取文件的MD5值
 *
 * @return MD5值
 */
fun File.encodeMd5(): String {
    var byteCount: Int
    val bytes = ByteArray(8192)
    val digester = MessageDigest.getInstance("Md5")
    FileInputStream(this).use {
        while (true) {
            byteCount = it.read(bytes)
            if (byteCount > 0) {
                digester.update(bytes, 0, byteCount)
            } else {
                break
            }
        }
    }
    return digester.digest().toHexString()
}

/**
 * @param url url
 * 根据url获取文件名
 * @return String
 */
fun getFileNameWithURL(url: String): String {
    return if (url.isNotEmpty()) {
        url.substring(url.lastIndexOf(File.separator) + 1)
    } else {
        "${System.currentTimeMillis()}"
    }
}

fun File.fileUri(): Uri {
    return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
        FileProvider.getUriForFile(getContextGlobal(), getContextGlobal().packageName, this)
    } else {
        Uri.fromFile(this)
    }
}

fun File.deepListFiles(
    fileArray: MutableList<File> = arrayListOf(), suffix: String = "",
): List<File> {
    if (isDirectory) {
        listFiles()?.forEach {
            it.deepListFiles(fileArray, suffix)
        }
    } else if (isFile) {
        if (suffix.isEmpty()) {
            fileArray.add(this)
        } else if (suffix.startsWith(".")) {
            if (".${this.extension}" == suffix) fileArray.add(this)
        } else {
            if (this.extension == suffix) fileArray.add(this)
        }
    }
    return fileArray
}

fun unzip(zipFile: File, destDir: File): List<File> {
    val files: MutableList<File> = ArrayList()
    try {
        val zip = ZipFile(zipFile)
        val entries: Enumeration<*> = zip.entries()
        zip.use {
            while (entries.hasMoreElements()) {
                val entry = entries.nextElement() as ZipEntry
                val entryName = entry.name.replace("\\", "/")
                if (entryName.contains("../")) {
                    Log.e(DEFAULT, "entryName: $entryName is dangerous!")
                    continue
                }
                unZipChildFile(destDir, files, it, entry, entryName)
            }
        }
    } catch (ignore: Throwable) {
    }
    return files
}

private fun unZipChildFile(
    destDir: File, files: MutableList<File>, zip: ZipFile, entry: ZipEntry, name: String,
) {
    val file = File(destDir, name).createFile(false)
    files.add(file)
    if (!entry.isDirectory) {
        BufferedOutputStream(FileOutputStream(file)).use { out ->
            BufferedInputStream(zip.getInputStream(entry)).use { input ->
                val buffer = ByteArray(8192)
                var len: Int
                while (input.read(buffer).also { len = it } != -1) {
                    out.write(buffer, 0, len)
                }
            }
        }
    }
}

fun getBytesFromFile(file: File): ByteArray? {
    if (file.exists()) {
        FileInputStream(file).use {
            return getBytesFromInputStream(it)
        }
    }
    return null
}

fun getBytesFromInputStream(inputStream: InputStream): ByteArray {
    ByteArrayOutputStream().use { bos ->
        val b = ByteArray(1024)
        var n: Int
        while (inputStream.read(b).also { n = it } != -1) {
            bos.write(b, 0, n)
        }
        return bos.toByteArray()
    }
}
